package com.pingan.haofang.searchcloud.metadata.service.impl;

import com.pingan.haofang.framework.common.functional.lang.control.bean.BeanCopy;
import com.pingan.haofang.searchcloud.api.StorageCluster;
import com.pingan.haofang.searchcloud.api.facade.CollectionConfigFacade;
import com.pingan.haofang.searchcloud.api.matedata.constant.MetadataFieldType;
import com.pingan.haofang.searchcloud.collection.dao.CollectionDao;
import com.pingan.haofang.searchcloud.collection.domain.Collection;
import com.pingan.haofang.searchcloud.common.constants.Status;
import com.pingan.haofang.searchcloud.common.dto.PageDTO;
import com.pingan.haofang.searchcloud.common.dto.RPCPageParams;
import com.pingan.haofang.searchcloud.common.rpc.RPC;
import com.pingan.haofang.searchcloud.common.rpc.RPCConfigProperties;
import com.pingan.haofang.searchcloud.index.constants.IndexConstants;
import com.pingan.haofang.searchcloud.index.dao.IndexDao;
import com.pingan.haofang.searchcloud.index.domain.Index;
import com.pingan.haofang.searchcloud.metadata.dao.MetadataDao;
import com.pingan.haofang.searchcloud.metadata.domain.Metadata;
import com.pingan.haofang.searchcloud.metadata.dto.MetadataFieldQueryResult;
import com.pingan.haofang.searchcloud.metadata.dto.MetadataFieldTypeQueryResult;
import com.pingan.haofang.searchcloud.metadata.dto.MetadataFieldsGenerateDTO;
import com.pingan.haofang.searchcloud.metadata.service.MetadataService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 索引字段类型Service的实现
 *
 * @author baojie371
 * @date 17/5/3
 * @since 1.0.0
 */
@Service
@ManagedResource
public class MetadataServiceImpl implements MetadataService {

    private static Logger LOG = LoggerFactory.getLogger(MetadataServiceImpl.class);

    private static final String SPLITOR = ",";

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private MetadataDao metadataDao;

    @Autowired
    private CollectionDao collectionDao;

    @Autowired
    private CollectionConfigFacade collectionConfigFacade;

    @Autowired
    private IndexDao indexDao;

    @Override
    public List<MetadataFieldTypeQueryResult> findAllTypes() {

        return Stream.of(MetadataFieldType.values())
                .map(type -> BeanCopy.of(type, new MetadataFieldTypeQueryResult())
                        .copy(BeanUtils::copyProperties)
                        .copy((s, t) -> t.setId(s.getValue()))
                        .get())
                .collect(Collectors.toList());
    }

    @Override
    public List<MetadataFieldQueryResult> generateFields(MetadataFieldsGenerateDTO fieldsGenerateDTO) {

        RPCConfigProperties config = new RPCConfigProperties();
        config.setUrl(fieldsGenerateDTO.getRpcUrl());
        config.setMethod(fieldsGenerateDTO.getRpcMethod());
        config.setSuccessCode("0");
        config.setJsonrpc("2.0");

        PageDTO<Object> indexRowDatas = RPC.executeForPage(restTemplate, config, Object.class,
                RPCPageParams.of(1, 1, 0,0));
        if (CollectionUtils.isEmpty(indexRowDatas.getDatas())) {
            return Collections.emptyList();
        }

        @SuppressWarnings("unchecked")
        Map<String, Object> resultMap = (Map<String, Object>) indexRowDatas.getDatas().get(0);
        if (resultMap == null || resultMap.isEmpty()) {
            return Collections.emptyList();
        }
        List<MetadataFieldQueryResult> results = new ArrayList<>(resultMap.size());
        resultMap.forEach((key, value) -> {
            MetadataFieldQueryResult result = new MetadataFieldQueryResult();
            result.setBoost(IndexConstants.METADATA_FIELD_DEFAULT_BOOST);
            result.setCopyToField("");
            result.setDefaultValue("");
            result.setFieldComment("");
            result.setFieldName(key);
            result.setIndexed(true);
            result.setStored(true);
            result.setMultiValued(false);
            result.setRequired(true);
            result.setTermVectors(false);
            result.setUniqueKey(false);

            if (value instanceof Integer) {
                result.setFieldTypeId(MetadataFieldType.INT.getValue());
            } else if (value instanceof Long) {
                result.setFieldTypeId(MetadataFieldType.LONG.getValue());
            } else if (value instanceof Float) {
                result.setFieldTypeId(MetadataFieldType.FLOAT.getValue());
            } else if (value instanceof Double) {
                result.setFieldTypeId(MetadataFieldType.DOUBLE.getValue());
            } else {
                result.setFieldTypeId(MetadataFieldType.STRING.getValue());
            }
        });
        results.get(0).setUniqueKey(true);

        return results;
    }

    @Override
    public List<Long> clearOldConfigOfStorage(long indexId, int engineType, int topN) {
        List<Metadata> metadatas = metadataDao.findByIndexIdAndStatusOrderByIdDesc(indexId, Status.STOP.getValue());
        int n = 0;
        List<Long> metadataIds = new ArrayList<>(metadatas.size());

        List<Collection> collections = collectionDao.findByIndexIdAndStatusOrderByIdDesc(indexId, Status.STOP.getValue());
        Set<Long> metadataIdSet = new HashSet<Long>();
        for (Collection collection : collections) {
            metadataIdSet.add(collection.getMetaId());
        }

        for (Metadata metadata : metadatas) {
            n++;
            if (n > topN && !metadataIdSet.contains(metadata.getId())) {
                try {
                    if (!collectionConfigFacade.delete(new StorageCluster(metadata.getHost(), engineType),
                            formatConfigNameInStorage(metadata))) {
                        LOG.error("Collection Delete Error");
                    }
                } catch (Exception e) {
                    LOG.error("Collection Delete Exception", e);
                }
                metadataIds.add(metadata.getId());
            }
        }
        return metadataIds;
    }

    @ManagedOperation
    public List<Long> clearUnusedConfig(int engineType) throws Exception {
        List<Metadata> metadatas = metadataDao.findByStatus(Status.DELETE.getValue());
        List<Long> metadataIds = new ArrayList<>(metadatas.size());
        for (Metadata metadata : metadatas) {
            if (indexDao.getOne(metadata.getIndexId()) == null) {
                LOG.info("index for {} not exist,ignore delete ", metadata);
            }
            try {
                if (!collectionConfigFacade.delete(new StorageCluster(metadata.getHost(), engineType),
                        formatConfigNameInStorage(metadata))) {
                    LOG.error("metadata Delete Error");
                }
            } catch (Exception e) {
                LOG.error("metadata Delete Exception", e);
            }
            metadataIds.add(metadata.getId());
        }
        return metadataIds;
    }

    @Override
    public void clearOldConfigOfDb(List<Long> metadataIds) {
        List<Metadata> metadatas = metadataDao.findAll(metadataIds);
        metadatas.forEach(collection -> collection.setStatus(Status.DELETE.getValue()));
        metadataDao.save(metadatas);
    }

    private String formatConfigNameInStorage(Metadata metadata) {
        return String.format("%s_%d", metadata.getConfigName(), metadata.getConfigVersion());
    }

    /**
     * 获取未使用config
     *
     * @param engineType
     * @param host
     * @return
     */
    @ManagedOperation
    public List<String> getUnusedConfigNames(int engineType, String host) throws Exception {
        StorageCluster storageCluster = new StorageCluster(host, engineType);
        List<String> configNames = collectionConfigFacade.listAllConfigNames(storageCluster);
        List<String> unUsedConfigNames = new ArrayList<>();
        for (String configName : configNames) {

            //判断是否满足未使用条件
            if(isUnUsedConfig(configName)){
                unUsedConfigNames.add(configName);
            }
        }
        LOG.info("unused configs size {}: {}", CollectionUtils.size(unUsedConfigNames), unUsedConfigNames);
        return unUsedConfigNames;
    }

    /**
     * 判断metadata是否满足未使用条件
     * @param name
     * @return
     */
    private Boolean isUnUsedConfig(String name) {
        //解析configname獲得meta名與版本
        Metadata metadata = getMetadataNameAndVersionFromConfigName(name);
        if(null == metadata) {
            return false;
        }

        //index表需要有记录
        if (getIndexNameFromMetadata(metadata.getConfigName()) == null) {
            return false;
        }

        List<Metadata> metadatas = metadataDao.findByConfigNameAndConfigVersion(metadata.getConfigName(), metadata.getConfigVersion());

        //数据库无config记录
        if (CollectionUtils.isEmpty(metadatas)) {
            return true;
        }
        //数据库config记录状态是删除
        Integer status = metadatas.get(0).getStatus();
        if (status == Status.DELETE.getValue()) {
            return true;
        }
        return false;

    }


    /**
     * 根据solr中configName查询数据库metadata
     *
     * @param
     * @return
     */
    @Override
    public Metadata getMetadataNameAndVersionFromConfigName(String configName) {
        Metadata metadata = new Metadata();
        String[] nameArr = configName.split("_");
        if (nameArr.length < 2 || !StringUtils.isNumeric(nameArr[nameArr.length - 1])) {
            LOG.info("config {} is not searchcloud config", configName);
            return null;
        }
        String[] expectedConfigNameArr = new String[nameArr.length - 1];
        System.arraycopy(nameArr, 0, expectedConfigNameArr, 0, expectedConfigNameArr.length);
        String expectedConfigName = StringUtils.join(expectedConfigNameArr, "_");

        metadata.setConfigName(expectedConfigName);
        metadata.setConfigVersion(Integer.parseInt(nameArr[nameArr.length - 1]));
        return metadata;
    }


    /**
     * 查询config对应的indexName(数据库存在)
     *
     * @param name
     * @return
     */
    @Override
    public Index getIndexNameFromMetadata(String name) {

        List<Index> indexList = indexDao.findByIndexName(name);
        if (CollectionUtils.isNotEmpty(indexList)) {
            return indexList.get(0);
        } else {
            LOG.info("config {} is not searchcloud config", name);
        }
        return null;
    }




    /**
     * 清理Solr里旧的Configs，无数据库关联
     *
     * @param engineType
     * @param host
     * @param configNamesStr 逗号分隔
     * @return
     */
    @ManagedOperation
    public List<String> clearOldConfigOfStorageForParamConfigNamesStr(int engineType, String host, String configNamesStr) {
        List<String> deleted = new ArrayList<>();

        String[] collectionNames = configNamesStr.split(SPLITOR);
        for (String name : collectionNames) {
            try {

                if (collectionConfigFacade.delete(new StorageCluster(host, engineType), name)) {
                    LOG.info("collection of engineType {} : {} deleted ", engineType, name);
                    deleted.add(name);
                }
            } catch (Exception e) {
                LOG.error("Collection Delete Exception", e);
            }
        }
        LOG.info("unused collection of engineType {} : {} deleted ", engineType, collectionNames);
        return deleted;
    }

    /**
     * 清理无用config
     *
     * @param engineType
     * @param host
     * @return
     */
    @ManagedOperation
    public List<String> clearUnusedConfigAfterListUnusedConfigs(int engineType, String host) throws Exception {
        List<String> configNames = getUnusedConfigNames(engineType, host);
        return clearOldConfigOfStorageForParamConfigNamesStr(engineType, host, StringUtils.join(configNames, SPLITOR));
    }
}
